﻿#pragma once

#include <queue>
#include <thread>
#include <unordered_map>
#include <functional>
#include <mutex>
#include <atomic>
#include "Device.h"

/*
    在本系统中，对"设备"这个词的定义：不是综合采集模块，而是具体的阀门、传感器等。
    要实现各个类型的设备自己管理自己————与硬件之间的通信，各自管各自的。
    那么，这里面就有一些问题：
        （1）所有的can设备都挂在总线上，与can总线上的设备通信，各个设备不能同时进行，而是需要排队
        （2）设备实际上接在综合采集模块上（或者类似的模块上），是需要通过综合采集模块的can通信协议完成通信
    我的想法：
        每个设备在can总线上有独立的id（可能由综合采集模块id + 设备通道id组成，但一定是全can总线唯一）;
        构建一个队列，放置需要向can总线发送的数据帧，排队顺序发送；
        每个设备类需要和实际设备通信时，将命令数据帧放入队列；
        每个设备类设置一个回调函数，当can总线接收到与该设备相关的数据包时，通过调用这个回调函数将数据包传递给设备类；
    这个CANHub类用于实现上面的can队列和can数据接收后回调函数的调用。
*/

//业务层、设备层的代码，尽量用标准c++书写，使用标准c++库。标准c++库同样是跨平台的。c++的版本考虑至少c++14
//将Qt的使用限制在界面端。这样可以让尽可能多的人读懂程序、修改程序。


namespace DeviceLayer {

//TODO:
//CAN消息结构体。
//这个结构体在这里声明，是为了让后面的代码不提示错误。
//include周立功的头文件后，将此删除。
struct can_frame
{
    uint32_t id;
//....
};


//回调函数类型定义，简化程序书写。回调函数应当返回void，接收一个参数：const can_frame;
using CANHubCallback = std::function<void(const can_frame)>;

/**
 * @brief CAN集线器类。我们将CAN集线器也作为一种设备，动态生成。
 */
class DeviceCANHub : public Device
{
    DECLARE_DYNAMIC_CREATE(DeviceCANHub)
protected:
    //用于放入can总线发送队列的can帧结构体
    struct CANMsgQueueInfo
    {
        can_frame msg;          //can数据帧
        bool waitForResponse;   //该帧发送出去后，是否要等待应答。如果为true，则队列挂起等待设备应答；如果为false，则不等待应答直接返回。
    };
    std::queue<CANMsgQueueInfo> msgSendQueue;   //发送队列
    std::mutex mtxQueue;                        //用于保证发送队列可重入的互斥量

    //当can总线上接收到设备的应答数据时，需要调用具体设备的回调函数，将应答数据帧传递给设备。
    //这个map用于建立can数据帧里的id与具体回调函数之间的映射。
    //key = id, value = 回调函数
    std::unordered_map<unsigned int, CANHubCallback> callbackMap;

    //can发送/接收线程
    std::thread canThread;
    void canThreadFunc();
    std::atomic<bool> run { false };

    //对于需要立即等待应答的数据帧，需要有阻塞机制。
    //这个enum定义几个阻塞状态。
    enum class BlockStatus
    {
        notBlocked,     //未阻塞
        blocking,       //正在阻塞，等待应答中
        overtime        //等待应答超时
    };
    //用于在can收发线程中记录当前在等待哪个设备应答，以及这个设备的阻塞状态
    volatile struct tagBlockingInfo
    {
        std::atomic<BlockStatus> blockStatus { BlockStatus::notBlocked };
        std::atomic<uint32_t> deviceBeingBlocked;
    } blockingInfo;

    std::condition_variable cv;

public:
    DeviceCANHub();
    ~DeviceCANHub();

    /**
     * @brief 读取SettingData.xml并初始化can总线。
     * 
     * @return true 初始化成功
     * @return false 初始化失败
     */
    virtual bool InitFromSettingData(const QString& path) override;

    virtual bool QueryData() final
    {
        return true;
    }

    /**
     * @brief 注册回调函数。每个can设备类的对象生成后，需要向CANHub注册自己的id和回调函数。
     * 
     * @param id 设备的can数据帧id
     * @param callBackFunc 设备的回调函数
     */
    inline void RegisterResponseCallback(const unsigned int id, CANHubCallback callBackFunc)
    {
        //在callbackMap里建立id到回调函数的映射。
        callbackMap.emplace(id, callBackFunc);
    }

    /**
     * @brief 发送数据帧
     * 
     * @param msg can数据帧
     * @param waitForResponse  是否需要等待应答。如果为true，则需要等待设备应答后，函数才返回；如果为false，则直接返回。 
     * @return true 在waitForResponse为true时，成功等待到设备应答。在waitForResponse为false时，直接返回true。
     * @return false 在waitForResponse为true时，未能等待到设备应答。
     */
    inline bool Send(const can_frame& msg, bool waitForResponse = false)
    {
        CANMsgQueueInfo msgQueueInfo;
        msgQueueInfo.msg = msg;
        msgQueueInfo.waitForResponse = waitForResponse;
        
        std::unique_lock<std::mutex> lock(mtxQueue);    //这里需要线程安全
        msgSendQueue.push(msgQueueInfo);    //将需要发送的消息压入队列

        if (waitForResponse)
        {
            //如果需要等待应答，那么，要等待can收发线程处理我这里压入队列的数据帧
            while (blockingInfo.deviceBeingBlocked != msg.id);
            //然后，要等待收发线程收到应答或者超时
            while (blockingInfo.blockStatus == BlockStatus::blocking);
            //通过condition variable通知主线程可以继续往前走了。
            cv.notify_all();
        }

        lock.unlock();
        return blockingInfo.blockStatus == BlockStatus::notBlocked;
    }
};
} // namespace BFOilPlatformDeviceLayer
